package com.example.lock.controller;

import com.example.lock.entity.CommodityDetails;
import com.example.lock.mapper.CommodityDetailsMapper;
import com.example.lock.redis.impl.GlobalRedisLockImpl;
import com.example.lock.redis.impl.LocalRedisLockImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @Description: redis 实现分布式锁
 * @Author: mingtian
 * @CreateDate: 2020/12/11 15:38
 * @Version: 1.0
 */
@RestController
public class RedisTaskController {

    /**
     * 打印日志
     */
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private CommodityDetailsMapper commodityDetailsMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private GlobalRedisLockImpl globalRedisLock;

    @Autowired
    private LocalRedisLockImpl localRedisLock;

    /**
     * redis 锁 key
     */
    private String redisLockKey = "redisLockKey";

    /**
     * 锁超时时间
     */
    private Long lockTimeOut = 30000000L;

    /**
     * 模拟秒杀场景  分布式锁 局部续命
     *
     * @param commodityId
     * @return
     */
    @GetMapping("/seckillWithLocalLock")
    public String seckillWithLocalLock(Long commodityId) {
        try {
            // 1.获取锁 设置超时时间 避免 死锁现象
            Boolean lock = localRedisLock.getLock();
            if (!lock) {
                logger.info("获取锁失败!");
                return "获取锁失败,请稍候重试!";
            }
            Thread.sleep(30000);
            // 2.查询商品库存信息
            CommodityDetails commodityDetails = commodityDetailsMapper.getCommodityDetails(commodityId);
            if (Objects.isNull(commodityDetails)) {
                return "该商品不存在!";
            }
            // 3.库存数量  如果库存大于0 进行扣库存处理
            Long stock = commodityDetails.getStock();
            if (stock > 0) {
                logger.info(">>>开始扣库存处理<<<");
                int result = commodityDetailsMapper.reduceInventory(commodityId);
                return result > 0 ? "秒杀成功!" : "秒杀失败!";
            }
            logger.info("扣库存失败,stockNum:{}", stock);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("errorInfo:{}", e);
        } finally {
            logger.info(">>>释放锁!<<<");
            // 释放锁
            localRedisLock.releaseLock();
        }
        return "秒杀失败!";
    }

    /**
     * 模拟秒杀场景  分布式锁  全局续命
     *
     * @param commodityId
     * @return
     */
    @GetMapping("/seckillWithGlobalLock")
    public String seckillWithGlobalLock(Long commodityId) {
        try {
            // 1.获取锁 设置超时时间 避免 死锁现象
            Boolean lock = globalRedisLock.getLock();
            if (!lock) {
                logger.info("获取锁失败!");
                return "获取锁失败,请稍候重试!";
            }
            Thread.sleep(999999999);
            // 2.查询商品库存信息
            CommodityDetails commodityDetails = commodityDetailsMapper.getCommodityDetails(commodityId);
            if (Objects.isNull(commodityDetails)) {
                return "该商品不存在!";
            }
            // 3.库存数量  如果库存大于0 进行扣库存处理
            Long stock = commodityDetails.getStock();
            if (stock > 0) {
                logger.info(">>>开始扣库存处理<<<");
                int result = commodityDetailsMapper.reduceInventory(commodityId);
                return result > 0 ? "秒杀成功!" : "秒杀失败!";
            }
            logger.info("扣库存失败,stockNum:{}", stock);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("errorInfo:{}", e);
        } finally {
            logger.info(">>>释放锁!<<<");
            // 释放锁
            globalRedisLock.releaseLock();
        }
        return "秒杀失败!";
    }

    /**
     * 模拟秒杀场景
     *
     * @param commodityId
     * @return
     */
    @GetMapping("/seckill")
    public String seckill(Long commodityId) {
        try {
            // 1.获取锁 设置超时时间 避免 死锁现象
            Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(redisLockKey, redisLockKey, lockTimeOut, TimeUnit.SECONDS);
            if (!lock) {
                logger.info("获取锁失败!");
                return "获取锁失败,请稍候重试!";
            }
            // 2.查询商品库存信息
            CommodityDetails commodityDetails = commodityDetailsMapper.getCommodityDetails(commodityId);
            if (Objects.isNull(commodityDetails)) {
                return "该商品不存在!";
            }
            // 3.库存数量  如果库存大于0 进行扣库存处理
            Long stock = commodityDetails.getStock();
            if (stock > 0) {
                logger.info(">>>开始扣库存处理<<<");
                int result = commodityDetailsMapper.reduceInventory(commodityId);
                return result > 0 ? "秒杀成功!" : "秒杀失败!";
            }
            logger.info("扣库存失败,stockNum:{}", stock);
        } catch (Exception e) {
            logger.error("errorInfo:{}", e);
        } finally {
            logger.info(">>>释放锁!<<<");
            // 释放锁
            stringRedisTemplate.delete(redisLockKey);
        }
        return "秒杀失败!";
    }

    /**
     * 模拟重入锁问题
     *
     * @param commodityId
     * @return
     */
    @GetMapping("/seckillWithRetryInLock")
    public String seckillWithRetryInLock(Long commodityId) {
        try {
            return seckillA(commodityId);
        } catch (Exception e) {
            logger.error("errorInfo:{}", e);
        } finally {
            logger.info(">>>释放锁!<<<");
            // 释放锁
            stringRedisTemplate.delete(redisLockKey);
        }
        return "秒杀失败!";
    }

    /**
     * 模拟重入锁问题
     *
     * @param commodityId
     */
    public String seckillA(Long commodityId) {
        // 1.获取锁
        boolean lock = globalRedisLock.getLock();
        if (!lock) {
            logger.info("获取锁失败!");
            return "获取锁失败,请稍后重试!";
        }
        // 2.查询商品库存信息
        CommodityDetails commodityDetails = commodityDetailsMapper.getCommodityDetails(commodityId);
        return seckillB(commodityDetails);
    }

    /**
     * 模拟重入锁问题
     *
     * @param commodityDetails
     */
    public String seckillB(CommodityDetails commodityDetails) {
        // 1.获取锁
        boolean lock = globalRedisLock.getLock();
        if (!lock) {
            logger.info("获取锁失败!");
            return "获取锁失败,请稍后重试!";
        }
        // 3.库存数量  如果库存大于0 进行扣库存处理
        Long stock = commodityDetails.getStock();
        if (stock > 0) {
            logger.info(">>>开始扣库存处理<<<");
            int result = commodityDetailsMapper.reduceInventory(commodityDetails.getId());
            return result > 0 ? "秒杀成功!" : "秒杀失败!";
        }
        logger.info("扣库存失败,stockNum:{}", stock);
        return "秒杀失败!";
    }
}